﻿HmJS.$Import('base.array');

/*
---
name: Vector

description: Provides a way to use Vectors in JavaScript

license: MIT-style

authors:
- Arian Stolwijk
- 苦苦的苦瓜

requires:
- Core/Class
- Core/Array
- Core/Number
- Array.Math
- Complex/Complex

provides:
- Vector

...
*/

HmJS.register('math.Vector', function ($) {
	var _abs = Math.abs,
			_prototype = Array.prototype;

	var Vector = new Class({

		elements: [],

		initialize: function () {
			this.setElements(arguments);
		},

		setElements: function () {
			var _from = Array.from,
					els = _from(arguments).flatten();
			if (els[0].elements) {
				els = els[0].elements;
			}
			if (typeof els[0] == 'string') {
				els = els[0].split(' ');
			}
			this.elements = _from(els.elements || els).map(function (e) {
				return Number.from(e);
			});
			return this;
		},

		getElement: function (i) {
			return (i < 1 || i > this.elements.length) ? null : this.elements[i - 1];
		},

		setElement: function (i, value) {
			var _els = this.elements;
			if (i >= 1 && i <= _els.length) {
				_els[i - 1] = Number.from(value);
			}
			return this;
		},

		getDimensions: function () {
			return this.elements.length;
		},

		getModulus: function () {
			return this.elements.vectorLength();
		},

		isEqual: function (vector) {
			var arr = vector.elements || vector,
					_els = this.elements,
					n = _els.length;
			if (arr.length != n) { return false; }
			var subtract = _els.subtract(vector);
			do {
				if (_abs(subtract[n] > Number.precision)) {
					return false;
				}
			} while (n--)
			return true;
		},

		clone: function () {
			return new Vector(this.elements);
		},

		map: function () {
			_prototype.map.apply(this.elements, arguments);
			return this;
		},

		each: function () {
			_prototype.each.apply(this.elements, arguments);
			return this;
		},

		toUnitVector: function () {
			this.elements.normalize();
		},

		getAngle: function (vector) {
			this.elements.angle(vector.elements || vector);
		},

		isParallelTo: function (vector) {
			var angle = this.getAngle(vector);
			return _abs(angle) <= Number.precision;
		},

		isAntiParallelTo: function (vector) {
			var angle = this.getAngle(vector);
			return _abs(angle - Math.PI) <= Number.precision;
		},

		isPerpendicularTo: function (vector) {
			return _abs(this.dot(vector)) <= Number.precision;
		},

		add: function () {
			this.elements = _prototype.add.apply(this.elements, arguments);
			return this;
		},

		subtract: function () {
			this.elements = _prototype.subtract.apply(this.elements, arguments);
			return this;
		},

		multiply: function () {
			this.elements = _prototype.product.apply(this.elements, arguments);
			return this;
		},

		dot: function (vector) {
			return _prototype.dot.apply(this.elements, [vector.elements || vector]);
		},

		cross: function (vector) {
			return _prototype.cross.apply(this.to3D().elements, [vector.to3D ? vector.to3D().elements : vector]);
		},

		max: function () {
			m = 0;
			this.map(function (e) {
				e = _abs(e);
				if (e > m) m = e;
			});
			return m;
		},

		indexOf: function (x) {
			return this.elements.indexOf(x);
		},

		round: function () {
			return this.map(function (x) {
				return Math.round(x);
			});
		},

		snapTo: function (x) {
			return this.map(function (e) {
				return _abs(e - x) <= Number.precision ? x : e;
			});
		},

		getDistanceFrom: function () {
			return _prototype.distance(this.elements, arguments);
		},

		to3D: function () {
			var els = this.elements,
					l = els.length;
			if (l < 3) {
				var n = 3 - l;
				do {
					els.push(0);
				} while (n--);
			}
			els = els.slice(0, 3);
			return this;
		},

		inspect: function (shrt) {
			return (shrt === false ? '' : '[') + this.elements.join(shrt === false ? '\n' : ', ') + (shrt === false ? '' : ']');
		},

		toArray: function () {
			return this.elements;
		}

	});

	Vector.extend({

		from: function () {
			var els = Array.from(arguments).flatten();
			if (typeOf(els[0]) == 'object' && Object.values) {
				els = Object.values(els[0]);
			}
			return new Vector(els);
		},

		Random: function (n) {
			var els = [];
			while (n--) {
				els.push(Math.random());
			}
			return new Vector(els);
		},

		Zero: function (n) {
			var els = [];
			while (n--) {
				els.push(0);
			}
			return new Vector(els);
		},

		i: new Vector('1 0 0'),
		j: new Vector('0 1 0'),
		k: new Vector('0 0 1')

	});

	return Vector;
});
